#!/usr/bin/python3
# -*- coding:utf-8 -*-
# @who:     spacelacc
# @when:    2025/04/26
# @what:    Widget for parsing spreadsheet files
# @vers:    V1


import os
import re
import xlrd
from PySide6.QtCore    import Signal
from PySide6.QtWidgets import QWidget, QStackedWidget, QDialog
from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout
from PySide6.QtWidgets import QListWidget, QPushButton, QLabel

from customfilebrowser import CustomFileBrowser
from customtableview   import CustomCounterTableView
from customtreeview    import CustomCounterTreeView


# get data from 'xls' spreadsheet file
class FileProbeDataParser():
    def __init__(self):
        self.assembler = []
        self.epsilon = 0.0000001
        self.columns = [3, 7, 11, 13, 15, 17, 19, 21]
        self.pattern = re.compile(r'\d{2}\d{2}\d{3}')

    def parse(self, files:list) -> None:
        # No file given, job done and return
        if not files:
            return

        # valid files found, clear old parsed data
        self.assembler.clear()

        # File given, extracting values
        count = 0
        for file in files:
            try:
                sheet = xlrd.open_workbook(file).sheets()[0]
#               count = 0
                for row in range(sheet.nrows):
                    serial = str(sheet.cell_value(row, 0))
                    if not self.pattern.match(serial):
                        continue

                    # We check for repeat here
#                   serial = serial[:-2]
                    found_copy = False
                    for i in range(len(self.assembler)):
#                       element = self.assembler[i]
#                       if serial == element[0]:
#                       if serial == self.assembler[i][0]:
                        if serial[:-2] == self.assembler[i][0]:
                            found_copy = True
                            break
                    if found_copy:
                        continue

                    newline = []
                    count += 1
                    # First 5 important parameters
                    newline.append(count)    # ID
#                   newline.append(serial)   # serial
                    newline.append(serial[:-2])  # serial
                    newline.append(True)     # default selected
                    newline.append(0)        # source kind
                    newline.append(file)     # source addr

                    error_detected = False
                    # Second 8 important parameters
                    for col in self.columns:
                        # We should check for 'data' here
                        if not isinstance(sheet.cell_value(row,col), float):
                            count -= 1
                            error_detected = True
                            break
                        data = sheet.cell_value(row,col)/1000000
                        newline.append(float(data))
                    if error_detected:
                        continue

                    # calculate slopes and evaluators
                    difference = 0
                    ev_sign = 0
                    ev_fully = 0   # evaluator fully
                    ev_fuzzy = 0   # evaluator fuzzy
                    sr_fully = 0   # squares fully
                    sr_fuzzy = 0   # squares fuzzy

                    #!!!! this line may be altered afterwards
                    for d in range(6, len(newline)):
                        difference = newline[d] - newline[d-1]
                        if difference > self.epsilon:
                            ev_sign = 2
                        elif difference < -self.epsilon:
                            ev_sign = 1
                        else:
                            ev_sign = 0
                        
                        # Calculating for 'fully'
                        ev_fully = 10 * ev_fully + ev_sign
                        sr_fully += difference ** 2

                        # Calculating for 'fuzzy'
                        if (d > 6):
                            ev_fuzzy = 10 * ev_fuzzy + ev_sign
                            sr_fuzzy += difference ** 2
                    
                    # Last 4 evaluators and least squares
                    newline.append(ev_fuzzy)
                    newline.append(ev_fully)
                    newline.append(sr_fuzzy * 10000)
                    newline.append(sr_fully * 10000)

                    # add this newline to 'queue'
                    self.assembler.append(newline)
            except xlrd.XLRDError:
                continue
            except OSError:
                continue

        # Deal with repeated serials

#   def get_parsed_data(self):
#       return self.assembler

    def get(self):
        return self.assembler



class FileProbeParser(QDialog):
    # Define widget signals here
    signalDataDone = Signal(list)
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.assembler = []   # data parsed from 'spreadsheet'
#       self.parser = CrawlerForSpreadsheet()
        self.parser = FileProbeDataParser()
        
        self.counter = CustomCounterTreeView()
        self.starter = QWidget()
        self.bt_submit_data = QPushButton('Submit Data')
        self.bt_clear_data  = QPushButton('Clear Data')
        self.bt_listdir = QPushButton('List Directory')
        self.bt_exit   = QPushButton('Exit')
        self.bt_submit_data.setObjectName(u'btParserSubmit')
        self.bt_clear_data.setObjectName(u'btParserClear')
        self.bt_listdir.setObjectName(u'btParserListdir')
        self.bt_exit.setObjectName(u'btParserExit')
        ctstarterbuttons = QHBoxLayout()
        ctstarterbuttons.setSpacing(9)
        ctstarterbuttons.setContentsMargins(6, 6, 6, 6)
        ctstarterbuttons.addStretch()
        ctstarterbuttons.addWidget(self.bt_submit_data)
        ctstarterbuttons.addWidget(self.bt_clear_data)
        ctstarterbuttons.addStretch()
        ctstarterbuttons.addWidget(self.bt_listdir)
        ctstarterbuttons.addWidget(self.bt_exit)
        ctstarter = QVBoxLayout(self.starter)
        ctstarter.setSpacing(0)
        ctstarter.setContentsMargins(0, 0, 0, 0)
        ctstarter.addStretch()
        ctstarter.addLayout(ctstarterbuttons)

        self.chooser = QWidget()
        self.browser = CustomFileBrowser()
        self.bt_file_confirm = QPushButton('Confirm')
        self.bt_file_cancel = QPushButton('Cancel')
        self.bt_file_confirm.setObjectName(u'btParserFileConfirm')
        self.bt_file_cancel.setObjectName(u'btParserFileCancel')
        ctbrowserbuttons = QHBoxLayout()
        ctbrowserbuttons.setSpacing(9)
        ctbrowserbuttons.setContentsMargins(0, 6, 6, 6)
        ctbrowserbuttons.addStretch()
        ctbrowserbuttons.addWidget(self.bt_file_confirm)
        ctbrowserbuttons.addWidget(self.bt_file_cancel)
        ctbrowser = QVBoxLayout(self.chooser)
        ctbrowser.setSpacing(0)
        ctbrowser.setContentsMargins(0, 0, 0, 0)
        ctbrowser.addWidget(self.browser)
        ctbrowser.addLayout(ctbrowserbuttons)

        self.checker = QWidget()
        self.preview = CustomCounterTableView()
        self.bt_view_confirm = QPushButton('Confirm')
        self.bt_view_cancel = QPushButton('Cancel')
        self.bt_view_confirm.setObjectName(u'btParserViewConfirm')
        self.bt_view_cancel.setObjectName(u'btParserViewCancel')
        ctpreviewbuttons = QHBoxLayout()
        ctpreviewbuttons.setSpacing(9)
        ctpreviewbuttons.setContentsMargins(0, 6, 6, 6)
        ctpreviewbuttons.addStretch()
        ctpreviewbuttons.addWidget(self.bt_view_confirm)
        ctpreviewbuttons.addWidget(self.bt_view_cancel)
        ctpreview = QVBoxLayout(self.checker)
        ctpreview.setSpacing(0)
        ctpreview.setContentsMargins(0, 0, 0, 0)
        ctpreview.addWidget(self.preview)
        ctpreview.addLayout(ctpreviewbuttons)

        self.switcher = QStackedWidget()
        self.switcher.addWidget(self.starter)
        self.switcher.addWidget(self.chooser)
        self.switcher.addWidget(self.checker)

        ctmaster = QHBoxLayout(self)
        ctmaster.setSpacing(0)
        ctmaster.setContentsMargins(0, 0, 0, 0)
        ctmaster.addWidget(self.counter)
        ctmaster.addWidget(self.switcher)
        ctmaster.setStretch(0, 1)
        ctmaster.setStretch(1, 4)

        self.bt_submit_data.clicked.connect(self.submitDataHandler)
        self.bt_clear_data.clicked.connect(self.clearDataHandler)
        self.bt_listdir.clicked.connect(self.listDirectoryHandler)
        self.bt_exit.clicked.connect(self.exitHandler)
        self.bt_file_confirm.clicked.connect(self.fileConfirmHandler)
        self.bt_file_cancel.clicked.connect(self.fileCancelHandler)
        self.bt_view_confirm.clicked.connect(self.viewConfirmHandler)
        self.bt_view_cancel.clicked.connect(self.viewCancelHandler)

    def clear(self):
        self.assembler.clear()
        self.countetr.tree_view_reset()
        self.preview.table_view_reset()

    def submitDataHandler(self):
        self.signalDataDone.emit(self.assembler)
        self.close()

    def clearDataHandler(self):
        self.assembler.clear()
        self.counter.tree_view_reset()
        self.preview.table_view_reset()

    def listDirectoryHandler(self):
        self.switcher.setCurrentIndex(1)

    def exitHandler(self):
        self.close()

    def fileConfirmHandler(self):
        files = self.browser.getSelected()
        self.parser.parse(files)
        buffer = self.parser.get()
        self.preview.table_view_renew(buffer)
        self.switcher.setCurrentIndex(2)
        
    def fileCancelHandler(self):
        self.switcher.setCurrentIndex(0)

    def viewConfirmHandler(self):
        # Merge newly parsed probes into 'assembler'
        for line in self.parser.get():
            if line[2] == True:
                self.assembler.append(line)

        # Here, we eliminate all repeated lines
        removed = []
        for i in range(len(self.assembler)):
            for line in self.assembler[i+1:]:
                if self.assembler[i][1] == line[1]:
                    removed.append(i)
                    break
        for j in reversed(removed):
            del self.assembler[j]

#       self.counter.treeview_renew(self.assembler)
        self.counter.tree_view_reset()
        self.counter.tree_view_update(self.assembler)
        self.preview.table_view_reset()
        self.switcher.setCurrentIndex(0)

    def viewCancelHandler(self):
        self.switcher.setCurrentIndex(1)

